/*
	Copyright (c) 2010 <copyright holders>
	This source code is licensed under The MIT License,
	see the file LICENSE for details.
	*/

#include "WorldScene.hpp"

#include <boost/bind.hpp>
#include <boost/math/tr1.hpp>

#include "EntityInterpolator.hpp" //temp
#include "Landscape.hpp"
#include "PresentationDefs.hpp" //temp
#include "Projectile.hpp" //temp
#include "UnitInterpolator.hpp"
#include "UnitInvariant.hpp" //temp
#include "World.hpp"
#include "WorldManager.hpp"

namespace spelprog2 {
namespace presentation {

WorldScene::WorldScene(sim::WorldManager & worldMan, Ogre::SceneManager & sceneMan) :
    worldMan_(worldMan),
    sceneMan_(sceneMan),
    activeSquadSelections_(worldMan, true),
    groupedSquadSelections_()
{
    worldMan.getSignals().worldUpdated.connect      (boost::bind(&WorldScene::onWorldUpdated, this, _1));
    worldMan.getSignals().unitAdded.connect         (boost::bind(&WorldScene::onUnitAdded, this, _1, _2, _3));
    worldMan.getSignals().unitRemoved.connect       (boost::bind(&WorldScene::onUnitRemoved, this, _1));
    worldMan.getSignals().projectileAdded.connect   (boost::bind(&WorldScene::onProjectileAdded, this, _1));
    worldMan.getSignals().projectileRemoved.connect (boost::bind(&WorldScene::onProjectileRemoved, this, _1));
}

WorldScene::~WorldScene()
{
}

void WorldScene::markSelectedUnits()
{
    for (UnitEntityMap::iterator it = unitEnts_.begin(), end = unitEnts_.end(); it != end; ++it) {
        sim::Squad::ConstPointer squad = it->first->getSquad().lock();
        const SquadSelection selection(squad->getPlayerId(), squad->getId());

        SelectionMarker marker = SELECTIONMARK_OFF;
        if (activeSquadSelections_.contains(selection)) {
            //select color by using the invariant that all units selected must have same alignment
            if (activeSquadSelections_.friendlySelected()) {
                marker = SELECTIONMARK_FRIENDLY;
            }
            else if (activeSquadSelections_.enemySelected()) {
                marker = SELECTIONMARK_ENEMY;
            }
        }

        it->second->setSelectionMarker(marker);
    }
}

void WorldScene::gfxUpdate(float deltaTime)
{
    for (UnitEntityMap::iterator it = unitEnts_.begin(), end = unitEnts_.end(); it != end; ++it) {
        it->second->gfxUpdate(deltaTime);
    }
}

void WorldScene::onWorldUpdated(const sim::World & world)
{
    typedef std::vector<sim::Unit::ConstPointer> UnitList;
    UnitList units;
    world.getUnits().all(*std::back_inserter(units));

    for (UnitList::const_iterator jt = units.begin(), end = units.end(); jt != end; ++jt) {
        UnitEntityMap::iterator it = unitEnts_.find(*jt);
        if (it == unitEnts_.end()) {
            continue;
        }

        it->second->onUnitUpdate(it->first);
    }

    sim::World::ConstProjectileRange projectiles = world.getProjectiles();
    for (sim::World::ConstProjectileIterator it = projectiles.first, end = projectiles.second; it != end; ++it) {
        //for now just update position
        ProjectileEntityMap::iterator jt = projectileEnts_.find(it->getId());
        if (jt == projectileEnts_.end()) {
            continue;
        }

        //for now manually update position, see if specialization is required later
        jt->second->setPosition(EntityInterpolator::convertPosition(it->getPosition()));
    }
}

void WorldScene::onUnitAdded(sim::Unit::ConstPointer unit, sim::Int playerId, sim::Int squadId)
{
    assert(unitEnts_.find(unit) == unitEnts_.end());

    UnitInterpolatorPtr unitEnt(new UnitInterpolator(unit, playerId, squadId,
        worldMan_.getWorld().getLandscape().getSpatials(), sceneMan_));
    unitEnts_.emplace(unit, unitEnt);
}

void WorldScene::onUnitRemoved(sim::Unit::ConstPointer unit)
{
    UnitEntityMap::iterator it = unitEnts_.find(unit);
    assert(it != unitEnts_.end());
    unitEnts_.erase(it);
}

void WorldScene::onProjectileAdded(const sim::Projectile & projectile)
{
    assert(projectileEnts_.find(projectile.getId()) == projectileEnts_.end());

    EntityInterpolatorPtr unitEnt(new EntityInterpolator(
        worldMan_.getWorld().getLandscape().getSpatials(),
        ENTITY_QUERY_FLAG_PROJECTILE,
        sceneMan_,
        projectile.getInvariant().getMeshName(),
        projectile.getInvariant().getMeshScale(),
        EntityInterpolator::convertPosition(projectile.getPosition()),
        Ogre::Radian(projectile.getInvariant().getMeshYaw())));
    projectileEnts_.emplace(projectile.getId(), unitEnt);
}

void WorldScene::onProjectileRemoved(const sim::Projectile & projectile)
{
    ProjectileEntityMap::iterator it = projectileEnts_.find(projectile.getId());
    assert(it != projectileEnts_.end());
    projectileEnts_.erase(it);
}

void WorldScene::clearSelectedSquads()
{
    activeSquadSelections_.clear();
    markSelectedUnits();
}

bool WorldScene::friendlySquadsSelected() const
{
    return activeSquadSelections_.friendlySelected();
}

void WorldScene::addToSelectedSquads(const SquadSelection & sel)
{
    if (activeSquadSelections_.add(sel)) {
        markSelectedUnits();
        //TODO: play selections sound? :p
    }
}

void WorldScene::removeFromSelectedSquads(const SquadSelection & sel)
{
    if (activeSquadSelections_.remove(sel)) {
        markSelectedUnits();
    }
}

SquadSelections::ConstIteratorRange WorldScene::getSelectedSquads() const
{
    return activeSquadSelections_.getAll();
}

void WorldScene::storeSelectedSquadsGroup(const std::string & key)
{
    groupedSquadSelections_.insert(std::make_pair(key, activeSquadSelections_));
}

void WorldScene::addGroupToSelectedSquads(const std::string & key)
{
    SquadSelectionsMap::const_iterator it = groupedSquadSelections_.find(key);
    if (it == groupedSquadSelections_.end()) {
        return;
    }

    bool changed = false;
    SquadSelections::ConstIteratorRange range = it->second.getAll();
    for (SquadSelections::ConstIterator it = range.first; it != range.second; ++it) {
        if (activeSquadSelections_.add(*it)) {
            changed = true;
        }
    }

    if (changed) {
        markSelectedUnits();
        //TODO: play selections sound? :p
    }
}

}; //presentation
}; //spelprog2
